home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
The Fatted Calf
/
The Fatted Calf.iso
/
Applications
/
Games
/
TileSlide
/
Source
/
TileControl.m
< prev
next >
Wrap
Text File
|
1993-07-10
|
38KB
|
1,122 lines
// TileSlide v1.0
// By Kevin Brain (ksbrain@zeus.UWaterloo.ca)
// with SoundGenerator class by Ali Ozer
// Released to the public domain September, 1991
#import "TileControl.h"
#import <appkit/nextstd.h> //imports math.h,stdio.h,libc.h
// (libc.h imports string.h)
#import <appkit/Box.h>
#import <appkit/Button.h>
#import <appkit/Cell.h>
#import <appkit/Text.h> // NXOrderStrings
#import <appkit/Matrix.h> // cellAt:: method
#import <appkit/Panel.h> // NXRunAlertPanel
#import <appkit/OpenPanel.h> // for loading background
#import <appkit/Application.h> // NX_BASETHRESHOLD
#import <appkit/FontManager.h> // for changing size of button fonts
#import <appkit/publicWraps.h> // for NXConvertWinNumToGlobal
#import <appkit/Listener.h> // for NX_WORKSPACEREQUEST
#import <appkit/Speaker.h> // for setSendPort
#import <defaults.h> // for using defaults system
#import <next/machparam.h> // for DELAY (playing ending tune)
// The scheme for the "music" is to play 2 note chords, where the notes are
// selected from arrays of chords. The musical key changes every NOTESPERBAR notes.
// The two arrays rowNotes and colNotes store the pitches. (The notes of the
// rowNotes array are generally lower in pitch). A note from each array is
// played whenever a tile is moved. The note from the rowNotes array is selected
// by the row number clicked, while the notes within a given key (row) of the
// colNotes array are played sequentially.
#define NUMBEROFBARS 8
#define NOTESPERBAR 4
#define COMPUTEBAR ((int)((numberOfMoves%(NUMBEROFBARS*NOTESPERBAR)) /NOTESPERBAR))
#define SEQUENTIALNOTES ((numberOfMoves%(NUMBEROFBARS*NOTESPERBAR)) %NOTESPERBAR)
#define ROWNOTE(k,n) (MKKeyNumToFreq (rowNotes[(k%NUMBEROFBARS)][(n%5)]))
#define COLNOTE(k,n) (MKKeyNumToFreq (colNotes[(k%NUMBEROFBARS)][(n%5)]))
@implementation TileControl
- appDidInit:sender
// responds as application's delegate
{
NXRect contentViewFrame;
NXSize theSize;
char titleTemp[3];
const char *tmpstr;
int i,row,col,x,y;
int tempIconPosition;
unsigned int windowNum;
id speaker = [NXApp appSpeaker];
static NXDefaultsVector TileSlideDefaults = {
{"Picture", ""},
{"Difficulty", "Normal"},
{"UsePictures", "YES"},
{"UseNumbers", "NO"},
{"EasyBests", "999,999,999,999"},
{"NormalBests", "999,999,999,999"},
{"HardBests", "999,999,999,999"},
{"BoardLocation","314 314"},
{NULL}};
NXRegisterDefaults([NXApp appName], TileSlideDefaults);
tileSlideCV = [slideWindowOut contentView];
infoPanelCV = [infoPanelOut contentView];
/* set initial difficulty */
tmpstr = NXGetDefaultValue ([NXApp appName], "Difficulty");
if (sscanf(tmpstr, "%d", &NumTilesX) != 1) NumTilesX = 4;
NumTilesY = NumTilesX;
/* retrieve bests for four challenges in each difficulty */
tmpstr = NXGetDefaultValue ([NXApp appName], "EasyBests");
if (sscanf(tmpstr, "%d %d %d %d", &bests[0][0],&bests[0][1],&bests[0][2],&bests[0][3]) != 4)
for (i=0;i<4;i++) bests[0][i] = MAXMOVES;
tmpstr = NXGetDefaultValue ([NXApp appName], "NormalBests");
if (sscanf(tmpstr, "%d %d %d %d", &bests[1][0],&bests[1][1],&bests[1][2],&bests[1][3]) != 4)
for (i=0;i<4;i++) bests[1][i] = MAXMOVES;
tmpstr = NXGetDefaultValue ([NXApp appName], "HardBests");
if (sscanf(tmpstr, "%d %d %d %d", &bests[2][0],&bests[2][1],&bests[2][2],&bests[2][3]) != 4)
for (i=0;i<4;i++) bests[2][i] = MAXMOVES;
for (row=0;row < 3;row++)
for (col=0; col < 4; col++)
if (bests[row][col] < MAXMOVES)
[[bestsMatrixOut cellAt:row:col] setIntValue:bests[row][col]];
else
[[bestsMatrixOut cellAt:row:col] setStringValue:"–"];
/* initialize info panel animation variables */
[infoPanelCV getFrame:&contentViewFrame];
stripe1Positiony = 5;
stripe2Positiony = (contentViewFrame.size.height-33);
stripe1Positionx = 5;
stripe2Positionx = (contentViewFrame.size.width-33);
stripe1Movement = 5;
stripe2Movement = -5;
tmpstr = NXGetDefaultValue ([NXApp appName], "Picture");
if ([self initAndCheckPicture:(char *)tmpstr] == NO) {
pictureLoaded = NO;
usePictures = NO;
useNumbers = YES;
[useNumbersSwitch setEnabled:NO];
[useNumbersSwitch setIntValue:1];
[usePicturesSwitch setEnabled:NO];
[usePicturesSwitch setIntValue:0];
tempIconPosition = NX_TITLEONLY;
}
else {
[picture getSize:(NXSize *)&theSize];
tmpstr = NXGetDefaultValue ([NXApp appName], "UsePictures");
if (NXOrderStrings((unsigned char *) "YES",(unsigned char *) tmpstr, YES, 4, NULL) == 0) {
usePictures = YES;
[usePicturesSwitch setIntValue:1];
}
else {
usePictures = NO;
[usePicturesSwitch setIntValue:0];
}
if ((theSize.height >= NUMBERMINHEIGHT) && (theSize.width >= NUMBERMINWIDTH)
&& ([usePicturesSwitch intValue] == 1))
[useNumbersSwitch setEnabled:YES];
else
[useNumbersSwitch setEnabled:NO];
tmpstr = NXGetDefaultValue ([NXApp appName], "UseNumbers");
if ((NXOrderStrings((unsigned char *) "YES",(unsigned char *) tmpstr, YES, 4, NULL) == 0)
&& (theSize.height >= NUMBERMINHEIGHT) && (theSize.width >= NUMBERMINWIDTH)) {
useNumbers = YES;
[useNumbersSwitch setIntValue:1];
[usePicturesSwitch setEnabled:YES];
if (usePictures == YES)
tempIconPosition = NX_ICONOVERLAPS;
else
tempIconPosition = NX_TITLEONLY;
}
else {
useNumbers = NO;
[useNumbersSwitch setIntValue:0];
[usePicturesSwitch setEnabled:NO];
if (usePictures == YES)
tempIconPosition = NX_ICONONLY;
else
tempIconPosition = NX_TITLEONLY;
}
tempIconPosition = NX_ICONONLY;
}
myFontManager = [FontManager new];
/* create the box that holds the tiles */
tileBox = [Box new];
[tileBox setBorderType:NX_BEZEL];
[tileBox setTitlePosition:NX_NOTITLE];
[tileBox setOffsets:(NXCoord)0 :(NXCoord)0];
/* create a box for erasing the old button position when a button is moved */
clearBox = [Box new];
[clearBox setBorderType:NX_NOBORDER];
[clearBox setTitlePosition:NX_NOTITLE];
[clearBox setOffsets:(NXCoord)0 :(NXCoord)0];
for (i=0; i<(MAXSIZEX*MAXSIZEY); i++) {
tile[i] = [Button new];
[tile[i] setAutodisplay:NO];
sprintf(titleTemp,"%d",i+1);
[tile[i] setTitle:titleTemp];
[tile[i] setIconPosition:tempIconPosition];
}
/* Create a matrix of invisible buttons that detect button presses */
/* Tag numbers of the form (row * MAXSIZEX + col) tell the slideTile: */
/* method where the button was pressed */
for (row=0; row<MAXSIZEY; row++)
for (col=0; col<MAXSIZEX; col++) {
tileInPosition[row][col].invisibleID = [Button new];
[tileInPosition[row][col].invisibleID setTransparent:(BOOL)YES];
[tileInPosition[row][col].invisibleID setTag:(row * MAXSIZEX + col)];
[tileInPosition[row][col].invisibleID setTarget:self];
[tileInPosition[row][col].invisibleID setAction:@selector(slideTile:)];
}
/* add the tile to the tileBox */
for (row=0; row<NumTilesY; row++)
for (col=0; col<NumTilesX; col++) {
[tileBox addSubview:tile[row * NumTilesX + col]];
}
[tile[NumTilesX*NumTilesY-1] removeFromSuperview]; // remove one for the space
/* add the invisible tiles to the tileBox (added last so they are on top) */
for (row=0; row<NumTilesY; row++)
for (col=0; col<NumTilesX; col++) {
[tileBox addSubview:tileInPosition[row][col].invisibleID];
}
[tileSlideCV addSubview:tileBox];
tmpstr = NXGetDefaultValue ([NXApp appName], "BoardLocation");
if (sscanf(tmpstr, "%d %d", &x,&y) != 2) { x = 314; y = 314; }
[slideWindowOut moveTo:(NXCoord)x :(NXCoord)y];
[self shuffle:mixPattern];
[self randomMix:self];
[self sizeSlideBoard];
[slideWindowOut orderFront:self];
/* register the tile slide window with the workspace (to accept pictures) */
/* (see Controller.m from Acceptor demo app) */
listener = [Listener new];
[listener setDelegate:self];
[listener usePrivatePort];
[listener addPort];
NXConvertWinNumToGlobal([slideWindowOut windowNum], &windowNum);
[speaker setSendPort:NXPortFromName(NX_WORKSPACEREQUEST, NULL)];
[speaker registerWindow:windowNum toPort:[listener listenPort]];
/* enable the soundGenerator object (get the DSP) */
if ([soundGenerator enable] == NO) {
NXRunAlertPanel(NULL,"Can't open the DSP.", "That's life.", NULL, NULL);
[soundSwitch setIntValue:0];
}
return self;
}
- appDidHide:app
// responds as application's delegate
{
[soundGenerator disable];
if ([infoPanelOut isVisible] == YES) // if info panel is open, animation is happening
DPSRemoveTimedEntry (infoTimer); // STOP IT!
return self;
}
- appDidUnhide:app
// responds as application's delegate
{
void infoAnimatorTimeout();
if ([soundGenerator enable] == NO) {
NXRunAlertPanel(NULL,"Can't open the DSP.", "That's life.", NULL, NULL);
[soundSwitch setIntValue:0];
}
if ([infoPanelOut isVisible] == YES) // if info panel is open, restart animation
infoTimer = DPSAddTimedEntry((double) INFOTIMERPERIOD, &infoAnimatorTimeout, self, NX_BASETHRESHOLD);
return self;
}
- init
// Initialize a few variables. Some of these things could be changed while
// running if a user interface to them is added (perhaps a preferrences panel)
{
[super init];
movementSteps = 3; // number of animation steps when moving tiles
currentStep = 0;
slidePeriod = .01; // seconds of delay between animation frames
topMargin = 15; // does not include window's titlebar
bottomMargin = 53; // between sizebar and bottom of tilebox
leftMargin = 10;
rightMargin = 15;
interTileDistance = 2;
buttonBorderWidth = 3; // lines which make up border of tile buttons
pictureType = TYPENONE;
infoTimer = NULL;
mixPattern = RANDOMFLAG;
solved = NO;
return self;
}
- free
{
int i,row,col;
[picture free];
[tileBox free];
[clearBox free];
[myFontManager free];
for (i=0; i<(MAXSIZEX*MAXSIZEY); i++)
[tile[i] free];
for (row=0; row<MAXSIZEY; row++)
for (col=0; col<MAXSIZEX; col++)
[tileInPosition[row][col].invisibleID free];
return [super free];
}
- openInfoPanel:sender
// show info panel and start info panel animation
{
void infoAnimatorTimeout();
[infoPanelOut orderFront:self];
if (!infoTimer)
infoTimer = DPSAddTimedEntry((double) INFOTIMERPERIOD, &infoAnimatorTimeout, self, NX_BASETHRESHOLD);
return self;
}
- windowWillClose:sender
// respond as delegate for info panel
{
DPSRemoveTimedEntry (infoTimer);
infoTimer = NULL;
return self;
}
- windowDidMove:sender;
// save new board position in defaults database
{
NXRect theRect;
char str[20];
[slideWindowOut getFrame:(NXRect *)&theRect];
sprintf (str, "%d %d",(int)theRect.origin.x,(int)theRect.origin.y);
NXWriteDefault ([NXApp appName], "BoardLocation", str);
return self;
}
void infoAnimatorTimeout (DPSTimedEntry timedEntry, double timeNow, void *data)
// dummy method to invoke changeInfoPanel method in response to info panel animation timeout
{
[(id)data changeInfoPanel];
}
- (void)changeInfoPanel
// Move buttons on info panel to create animation!!
{
NXRect contentViewFrame;
[infoPanelCV getFrame:&contentViewFrame];
[stripe1Blanker moveTo:(NXCoord)stripe1Positionx:(NXCoord)stripe1Positiony];
if (stripe1Positiony+stripe1Movement < 5)
stripe1Movement = -stripe1Movement;
if (stripe1Positiony+stripe1Movement+33 > contentViewFrame.size.height)
stripe1Movement = -stripe1Movement;
stripe1Positiony += stripe1Movement;
[stripe1 moveTo:(NXCoord)stripe1Positionx :(NXCoord)stripe1Positiony];
[stripe1Blanker display];
[stripe1 display];
[stripe2Blanker moveTo:(NXCoord)stripe2Positionx:(NXCoord)stripe2Positiony];
if (stripe2Positiony+stripe2Movement < 5)
stripe2Movement = -stripe2Movement;
if (stripe2Positiony+stripe2Movement+33 > contentViewFrame.size.height)
stripe2Movement = -stripe2Movement;
stripe2Positiony += stripe2Movement;
[stripe2 moveTo:(NXCoord)stripe2Positionx :(NXCoord)stripe2Positiony];
[stripe2Blanker display];
[stripe2 display];
return;
}
- infoPanelNotes:sender
// plays a random 2-note chord when a button on info panel is pressed
{
int key;
key = random() % NUMBEROFBARS;
return self;
}
- openBackground:sender
// select background to load via open panel
{
char *theFile,*theDirectory;
const char *const *returnedFileList;
char **fileTypeList;
char *typeList[3];
NXSize theSize;
int length;
typeList[0] = "tiff";
typeList[1] = "eps";
typeList[2] = 0;
fileTypeList = typeList;
myOpenPanel = [OpenPanel new];
[myOpenPanel allowMultipleFiles:(BOOL)NO];
if ([myOpenPanel runModalForTypes:(const char *const *)fileTypeList] == 0)
return self; // CANCEL selected
returnedFileList = [myOpenPanel filenames];
if (returnedFileList == NULL) return self;
theFile = (char *) *returnedFileList;
theDirectory = (char *) [myOpenPanel directory];
length = (strlen(theFile) + strlen(theDirectory)+1);
if (filePathLength <= length) {
if (filePath) {
free(filePath);
}
filePath = (char *)malloc(length + 1);
filePathLength = length;
}
strcpy(filePath, theDirectory);
strcat(filePath, "/");
strcat(filePath, theFile);
if ([self initAndCheckPicture:(char *)filePath] == NO)
return self; // selected file is not loadable
else {
if (usePictures == NO){
usePictures = YES;
[usePicturesSwitch setIntValue:1];
NXWriteDefault ([NXApp appName], "UsePictures", "YES");
}
[picture getSize:&theSize];
[picture getSize:(NXSize *)&theSize];
if ((theSize.height >= NUMBERMINHEIGHT) && (theSize.width >= NUMBERMINWIDTH)) {
[useNumbersSwitch setEnabled:YES];
if (useNumbers == YES)
[usePicturesSwitch setEnabled:YES];
}
else { // picture too small to allow using numbers
useNumbers = NO;
[useNumbersSwitch setEnabled:NO];
[useNumbersSwitch setIntValue:0];
[usePicturesSwitch setEnabled:NO];
}
[self sizeSlideBoard];
}
return self;
}
- togglePictures:sender
// Toggles pictures on and off. Receives action messages from the "Picture" switch.
{
if ([sender intValue] == 1) {
usePictures = YES;
NXWriteDefault ([NXApp appName], "UsePictures", "YES");
[useNumbersSwitch setEnabled:YES];
}
else {
usePictures = NO;
NXWriteDefault ([NXApp appName], "UsePictures", "NO");
[useNumbersSwitch setEnabled:NO];
};
[self sizeSlideBoard];
return self;
}
- toggleNumbers:sender
// Toggles visibility of numbers on and off.
// Receives action messages from the "Numbers" switch.
{
if ([sender intValue] == 1) {
useNumbers = YES;
NXWriteDefault ([NXApp appName], "UseNumbers", "YES");
if (pictureLoaded == YES)
[usePicturesSwitch setEnabled:YES];
}
else {
useNumbers = NO;
NXWriteDefault ([NXApp appName], "UseNumbers", "NO");
[usePicturesSwitch setEnabled:NO];
};
[self sizeSlideBoard];
return self;
}
- sizeSlideBoard
// Draws the tile board at the correct size, with the NumTilesX * NumTilesY tiles
// Determines the size from the current size of "picture" if the "Picture" switch
// is checked, and from the size of the window if not.
{
NXRect tempRect;
NXSize tempSize;
NXPoint tempPoint;
id tempImage,newFont;
int i,row,col,minSize;
float tempFontSize;
if (usePictures == YES) {
[slideWindowOut disableFlushWindow];
[picture getSize:&tempSize];
sizex = (int)((tempSize.width/NumTilesX)-buttonBorderWidth);
sizey = (int)((tempSize.height/NumTilesY)-buttonBorderWidth);
tempSize.width = (NXCoord)sizex;
tempSize.height = (NXCoord)sizey;
tempRect.size = tempSize;
if (sizex>sizey) minSize = sizey;
else minSize = sizex;
tempFontSize = (float)((minSize-5+buttonBorderWidth)/1.6);
newFont = [myFontManager findFont:(const char *)"Helvetica" traits:
(NXFontTraitMask)NX_UNBOLD weight:(int)0 size:tempFontSize];
for (row=0; row<NumTilesY; row++)
for (col=0; col<NumTilesX; col++){
tempPoint.x = (NXCoord)((sizex+buttonBorderWidth) * col);
tileInPosition[row][col].xPosition = tempPoint.x;
tempPoint.y = (NXCoord)((sizey+buttonBorderWidth) * (NumTilesY-row-1));
tileInPosition[row][col].yPosition = tempPoint.y;
tempRect.origin = tempPoint;
tempImage = [[NXImage alloc] initFromImage:(NXImage *)picture rect:(const NXRect *)&tempRect];
[tile[row*NumTilesX+col] setImage:tempImage];
[tile[row*NumTilesX+col] sizeTo:(NXCoord)(sizex+buttonBorderWidth)
:(NXCoord)(sizey+buttonBorderWidth)];
[tile[tileInPosition[row][col].tileNumber] moveTo:
(tileInPosition[row][col].xPosition) :(tileInPosition[row][col].yPosition)];
[tileInPosition[row][col].invisibleID moveTo:
(tileInPosition[row][col].xPosition) :(tileInPosition[row][col].yPosition)];
[tileInPosition[row][col].invisibleID sizeTo:
(NXCoord)(sizex+buttonBorderWidth) :(NXCoord)(sizey+buttonBorderWidth)];
if (useNumbers == YES)
[tile[row*NumTilesX+col] setIconPosition:NX_ICONOVERLAPS];
else
[tile[row*NumTilesX+col] setIconPosition:NX_ICONONLY];
}
[clearBox sizeTo:(NXCoord)(sizex+buttonBorderWidth) :(NXCoord)(sizey+buttonBorderWidth)];
[tileBox setBorderType:NX_BEZEL];
[tileBox sizeToFit];
[tileBox getFrame:&tempRect];
windowSize.width = (NXCoord)(tempRect.size.width + leftMargin + rightMargin);
windowSize.height = (NXCoord)(tempRect.size.height + topMargin + bottomMargin);
[slideWindowOut sizeWindow:windowSize.width :windowSize.height];
[tileBox moveTo:(NXCoord)leftMargin :(NXCoord)bottomMargin];
// I don't think the display in the following loop should be necessary, but set font
// doesn't seem to work unless you do a display after setting icon position
if (useNumbers == YES) {
for (i=0; i<(MAXSIZEX*MAXSIZEY); i++) {
[tile[i] display];
[tile[i] setFont:newFont];
}
}
[slideWindowOut reenableFlushWindow];
[slideWindowOut display];
}
else {
[slideWindowOut disableFlushWindow];
[tileSlideCV getFrame:&tempRect];
sizex = (int) ((tempRect.size.width - leftMargin - rightMargin -((NumTilesX-1) * interTileDistance))/NumTilesX);
sizey = (int) ((tempRect.size.height - topMargin - bottomMargin -((NumTilesY-1) * interTileDistance))/NumTilesY);
for (row=0; row<NumTilesY; row++)
for (col=0; col<NumTilesX; col++){
tileInPosition[row][col].xPosition =(NXCoord)((sizex+interTileDistance) * col);
tileInPosition[row][col].yPosition =(NXCoord)((sizey+interTileDistance) * (NumTilesY-row-1));
[tile[tileInPosition[row][col].tileNumber] moveTo:
(tileInPosition[row][col].xPosition) :(tileInPosition[row][col].yPosition)];
[tileInPosition[row][col].invisibleID moveTo:
(tileInPosition[row][col].xPosition) :(tileInPosition[row][col].yPosition)];
[tile[tileInPosition[row][col].tileNumber] sizeTo:(NXCoord)sizex :(NXCoord)sizey];
[tileInPosition[row][col].invisibleID sizeTo:(NXCoord)sizex :(NXCoord)sizey];
}
if (sizex>sizey) minSize = sizey;
else minSize = sizex;
for (i=0; i<(MAXSIZEX*MAXSIZEY); i++) {
[tile[i] setIconPosition:NX_TITLEONLY];
[tile[i] display];
[tile[i] setFont:[myFontManager findFont:(const char *)"Helvetica" traits:(NXFontTraitMask)NX_UNBOLD weight:(int)0 size:(float)((minSize-5)/1.6)]];
}
[clearBox sizeTo:(NXCoord)sizex :(NXCoord)sizey];
[tileBox setBorderType:NX_BEZEL];
[tileBox sizeToFit];
[tileBox moveTo:(NXCoord)leftMargin :(NXCoord)bottomMargin];
[slideWindowOut reenableFlushWindow];
[slideWindowOut display];
}
return self;
}
- slideTile:sender
// Receives action messages from the invisible buttons over the tiles
// Starts sliding animation timer if sliding is to be done.
{
void slideTimeout ();
int row,col;
if (solved == YES) return self;
if (sliding == YES) return self;
row = (int) [sender tag]/MAXSIZEX;
col = (int) [sender tag]-(row*MAXSIZEX);
if ((rowOfSpace != row) && (colOfSpace != col)) return self;
if ((rowOfSpace == row) && (colOfSpace == col)) return self;
if (rowOfSpace == row) {
slideRowOrCol = ROW;
howManyToMove = colOfSpace - col;
}
if (colOfSpace == col) {
slideRowOrCol = COL;
howManyToMove = rowOfSpace - row;
}
numberOfMoves++;
[movesOut setIntValue:numberOfMoves];
sliding = (BOOL)YES;
fromRow = row;
fromCol = col;
slideTimer = DPSAddTimedEntry((double) slidePeriod, &slideTimeout, self, NX_BASETHRESHOLD);
return self;
}
void slideTimeout (DPSTimedEntry timedEntry, double timeNow, void *data)
// Dummy method to invoke slideABit method in response to tile sliding timeout
{
[(id)data slideABit];
}
- (void) slideABit
// Slide tiles one step; re-arrange position of tiles when done sliding.
// Check for puzzle solved, congratulate and re-shuffle if it is
{
int distance,i,direction;
int row,col,difficulty;
char str[100];
currentStep++;
if (howManyToMove < 0)
direction = (-1);
else
direction = 1;
if (currentStep == movementSteps) { // done sliding; re-arrange position of tiles
DPSRemoveTimedEntry (slideTimer);
sliding = (BOOL)NO;
if (slideRowOrCol == ROW) {
for (i=(fromCol + howManyToMove);i!= fromCol;i=i-direction) {
[tile[tileInPosition[fromRow][i-direction].tileNumber] addSubview:clearBox];
[clearBox display];
[clearBox removeFromSuperview];
tileInPosition[fromRow][i].tileNumber = tileInPosition[fromRow][i-direction].tileNumber;
[tile[tileInPosition[fromRow][i].tileNumber] moveTo:
(tileInPosition[fromRow][i].xPosition) :(tileInPosition[fromRow][i].yPosition)];
[tile[tileInPosition[fromRow][i].tileNumber] display];
}
}
else {
for (i=(fromRow + howManyToMove);i!= fromRow;i=i-direction) {
[tile[tileInPosition[i-direction][fromCol].tileNumber] addSubview:clearBox];
[clearBox display];
[clearBox removeFromSuperview];
tileInPosition[i][fromCol].tileNumber = tileInPosition[i-direction][fromCol].tileNumber;
[tile[tileInPosition[i][fromCol].tileNumber] moveTo:
(tileInPosition[i][fromCol].xPosition) :(tileInPosition[i][fromCol].yPosition)];
[tile[tileInPosition[i][fromCol].tileNumber] display];
}
}
tileInPosition[fromRow][fromCol].tileNumber = NumTilesX*NumTilesY-1;
// check for puzzle solved
solved = YES;
for (row=0; row<NumTilesY; row++)
for (col=0; col<NumTilesX; col++)
if (tileInPosition[row][col].tileNumber != (row * NumTilesX + col)) solved = NO;
rowOfSpace = fromRow;
colOfSpace = fromCol;
if (solved == YES) {
[tileInPosition[NumTilesY-1][NumTilesX-1].invisibleID removeFromSuperview];
[tileBox addSubview:tile[NumTilesX*NumTilesY-1]];
[tileBox addSubview:tileInPosition[NumTilesY-1][NumTilesX-1].invisibleID];
[tile[NumTilesX*NumTilesY-1] moveTo:(tileInPosition[NumTilesY-1][NumTilesX-1].xPosition)
:(tileInPosition [NumTilesY-1][NumTilesX-1].yPosition)];
[tile[NumTilesX*NumTilesY-1] display];
[self playFinishMusic];
difficulty = NumTilesX-3;
if ((mixPattern != RANDOMFLAG) && (numberOfMoves < bests[difficulty][mixPattern])) {
bests[difficulty][mixPattern] = numberOfMoves;
[[bestsMatrixOut cellAt:difficulty:mixPattern]
setIntValue:bests[difficulty][mixPattern]];
[[bestsMatrixOut cellAt:difficulty:mixPattern] setBackgroundGray:(float)NX_WHITE];
[bestsPanelOut orderFront:self];
NXRunAlertPanel("Congratulations!",
"You have set a new 'best'!", "Yippee!", NULL, NULL);
switch (difficulty) {
case 0:
sprintf (str, "%d %d %d %d\0",
bests[0][0], bests[0][1], bests[0][2], bests[0][3]);
NXWriteDefault ([NXApp appName], "EasyBests", str);
break;
case 1:
sprintf (str, "%d %d %d %d\0",
bests[1][0], bests[1][1], bests[1][2], bests[1][3]);
NXWriteDefault ([NXApp appName], "NormalBests", str);
break;
case 2:
sprintf (str, "%d %d %d %d\0",
bests[2][0], bests[2][1], bests[2][2], bests[2][3]);
NXWriteDefault ([NXApp appName], "HardBests", str);
break;
}
[[bestsMatrixOut cellAt:difficulty:mixPattern] setBackgroundGray:(float)NX_LTGRAY];
}
}
currentStep = 0;
}
else
if (slideRowOrCol == ROW) { // not done sliding; move tiles one step
distance = (int) (((float)currentStep/(float)movementSteps) * sizex * direction);
for (i=(fromCol + howManyToMove-direction);i!= fromCol-direction;i=i-direction) {
[tile[tileInPosition[fromRow][i].tileNumber] addSubview:clearBox];
[clearBox display];
[clearBox removeFromSuperview];
[tile[tileInPosition[fromRow][i].tileNumber] moveTo:
(NXCoord)(tileInPosition[fromRow][i].xPosition + distance) :(tileInPosition[fromRow][i].yPosition)];
[tile[tileInPosition[fromRow][i].tileNumber] display];
}
}
else {
distance = (int) (((float)currentStep/(float)movementSteps)*sizey*direction * -1);
for (i=(fromRow + howManyToMove-direction);i!= fromRow-direction;i=i-direction) {
[tile[tileInPosition[i][fromCol].tileNumber] addSubview:clearBox];
[clearBox display];
[clearBox removeFromSuperview];
[tile[tileInPosition[i][fromCol].tileNumber] moveTo:
(tileInPosition[i][fromCol].xPosition) :(NXCoord)(tileInPosition[i][fromCol].yPosition + distance)];
[tile[tileInPosition[i][fromCol].tileNumber] display];
}
}
return;
}
- playFinishMusic
// plays the run of notes when the puzzle is solved
{
return self;
}
- selectSize:sender
// Receives action messages from the "Difficulty" menu cells
// Selects x and y size of tile board (currently the same) and invokes "shuffle:"
{
int row,col;
char str[3];
for (row=0; row<NumTilesY; row++)
for (col=0; col<NumTilesX; col++){
[tile[tileInPosition[row][col].tileNumber] removeFromSuperview];
[tileInPosition[row][col].invisibleID removeFromSuperview];
}
[tileBox setBorderType:NX_NOBORDER]; // clear old board
[tileSlideCV display];
// set number of tiles
NumTilesX = [[sender selectedCell] tag];
NumTilesY = NumTilesX;
sprintf (str, "%d\0", NumTilesX);
NXWriteDefault ([NXApp appName], "Difficulty", str);
for (row=0; row<NumTilesY; row++)
for (col=0; col<NumTilesX; col++) {
tileInPosition[row][col].tileNumber = (row * NumTilesX)+ col;
[tileBox addSubview:tile[tileInPosition[row][col].tileNumber]];
}
[tile[NumTilesX*NumTilesY-1] removeFromSuperview];
for (row=0; row<NumTilesY; row++) // invisible buttons added last so they are on top!
for (col=0; col<NumTilesX; col++) {
[tileBox addSubview:tileInPosition[row][col].invisibleID];
}
[tile[NumTilesX*NumTilesY-1] removeFromSuperview];
if (mixPattern != RANDOMFLAG) {
[self shuffle:mixPattern];
[self sizeSlideBoard];
[slideWindowOut orderFront:self];
}
else {
[self shuffle:mixPattern];
[self randomMix:self];
}
return self;
}
- shuffle:(int)pattern
// shuffle the board (to a default shuffled pattern) and reset numberOfMoves
// Currently, this only handles 3*3, 4*4, and 5*5 boards
{
static int scrambledSize5[NUMOFCHALLENGES][25]=
{{24,10,8,22,5,7,15,16,20,23,17,12,2,14,6,3,13,4,18,1,0,19,11,9,21},
{24,10,14,13,22,9,21,1,3,11,4,8,16,23,18,2,6,20,15,17,0,7,19,5,12},
{24,19,14,9,4,23,18,13,8,3,22,17,12,7,2,21,16,11,6,1,20,15,10,5,0},
{24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0}};
static int scrambledSize4[NUMOFCHALLENGES][16]=
{{15,14,3,13,0,9,11,4,10,7,5,2,8,1,6,12},
{15,3,5,13,1,11,12,2,4,10,0,8,6,14,9,7},
{15,11,7,3,14,10,6,2,13,9,5,1,12,8,4,0},
{15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0}};
static int scrambledSize3[NUMOFCHALLENGES][9]=
{{8,0,4,2,5,3,6,1,7},
{8,1,5,6,4,3,0,2,7},
{8,5,2,6,4,1,7,3,0},
{8,1,5,7,4,6,2,3,0}};
int row,col,patternUsed;
patternUsed = pattern % NUMOFCHALLENGES;
switch (NumTilesX) {
case 3:
for (row=0;row<NumTilesY;row++)
for (col=0;col<NumTilesX;col++)
tileInPosition[row][col].tileNumber =
(int)scrambledSize3[patternUsed][col + (row *NumTilesX)];
break;
case 4:
for (row=0;row<NumTilesY;row++)
for (col=0;col<NumTilesX;col++)
tileInPosition[row][col].tileNumber =
(int)scrambledSize4[patternUsed][col + (row *NumTilesX)];
break;
case 5:
for (row=0;row<NumTilesY;row++)
for (col=0;col<NumTilesX;col++)
tileInPosition[row][col].tileNumber =
(int)scrambledSize5[patternUsed][col + (row *NumTilesX)];
break;
}
rowOfSpace = 0;
colOfSpace = 0;
numberOfMoves = 0;
[movesOut setIntValue:numberOfMoves];
solved = NO;
return self;
}
- challengeMix:sender
// select next pattern, call shuffle, change window title, and redraw
// target of the 'Challenge Mix' menu item
{
char buffer[30],bufferNum[5];
[tile[NumTilesX*NumTilesY-1] removeFromSuperview];
solved = NO;
if (mixPattern == RANDOMFLAG)
mixPattern = NUMOFCHALLENGES; // wraps around when used
mixPattern ++;
if (mixPattern >= NUMOFCHALLENGES) mixPattern = 0;
[self shuffle:mixPattern];
strcpy(buffer,"Tile Slide - Challenge#");
sprintf(bufferNum," %d", (mixPattern+1));
strcat(buffer,bufferNum);
[slideWindowOut setTitle:(const char *)buffer];
[self sizeSlideBoard];
[slideWindowOut orderFront:self];
return self;
}
- randomMix:sender
// make 100 random slides, change window title, and redraw
// target of the 'Random Mix' menu item
{
int i,dir,tempRow,tempCol;
int tempTileNum;
static int moves[4][2] = {{0,1},{1,0},{0,-1},{-1,0}};
char buffer[30];
[tile[NumTilesX*NumTilesY-1] removeFromSuperview];
solved = NO;
mixPattern = RANDOMFLAG;
for (i=0;i<100;i++) {
dir = random() % 4;
while ((rowOfSpace+moves[dir][1] >= NumTilesY) || (rowOfSpace+moves[dir][1] < 0) ||
(colOfSpace+moves[dir][0] >= NumTilesX) || (colOfSpace+moves[dir][0] < 0))
dir = random() % 4;
tempRow = rowOfSpace+ moves[dir][1];
tempCol = colOfSpace+moves[dir][0];
tempTileNum = tileInPosition[tempRow][tempCol].tileNumber;
tileInPosition[tempRow][tempCol].tileNumber = tileInPosition[rowOfSpace][colOfSpace].tileNumber;
tileInPosition[rowOfSpace][colOfSpace].tileNumber = tempTileNum;
rowOfSpace = tempRow;
colOfSpace = tempCol;
}
strcpy(buffer,"Tile Slide - Random");
[slideWindowOut setTitle:(const char *)buffer];
numberOfMoves = 0;
[movesOut setIntValue:numberOfMoves];
[self sizeSlideBoard];
[slideWindowOut orderFront:self];
return self;
}
- (int)iconEntered:(int)windowNum at:(double)x :(double)y
iconWindow:(int)iconWindowNum iconX:(double)iconX iconY:(double)iconY
iconWidth:(double)iconWidth iconHeight:(double)iconHeight
pathList:(char *)pathList
// respond as delegate for main window
{
char *stringPosition;
int length;
/* save the file's path for later */
length = strlen(pathList);
if (filePathLength <= length) {
if (filePath) {
free(filePath);
}
filePath = (char *)malloc(length + 1);
filePathLength = length;
}
strcpy(filePath, pathList);
stringPosition = filePath;
/* the number of tabs + 1 equals the number of files dragged in */
files = 1;
while (stringPosition = index(stringPosition, '\t')) {
files++;
stringPosition++;
}
return 0;
}
- (int)iconReleasedAt:(double)x :(double)y ok:(int *)flag
// respond as delegate for main window
{
NXSize theSize;
if (files!=1) {
*flag = 0; // tell Workspace to animate the ICON back to its source window
return 0;
}
if ([self initAndCheckPicture:(char *)filePath] == NO) {
*flag = 0; // do not accept the icon
}
else {
*flag = 1; // accept the icon
if (usePictures == NO){
usePictures = YES;
[usePicturesSwitch setIntValue:1];
NXWriteDefault ([NXApp appName], "UsePictures", "YES");
}
[picture getSize:&theSize];
[picture getSize:(NXSize *)&theSize];
if ((theSize.height >= NUMBERMINHEIGHT) && (theSize.width >= NUMBERMINWIDTH)) {
[useNumbersSwitch setEnabled:YES];
if (useNumbers == YES)
[usePicturesSwitch setEnabled:YES];
}
else
{ // picture too small to allow using numbers
useNumbers = NO;
[useNumbersSwitch setEnabled:NO];
[useNumbersSwitch setIntValue:0];
[usePicturesSwitch setEnabled:NO];
}
[self sizeSlideBoard];
}
return 0;
}
- (BOOL)initAndCheckPicture:(char *)pictureName
// Checks out the dragged in icon. Makes sure it is tiff or eps
// and resizes if too big or small. Returns YES if it loads a picture.
{
NXStream *input;
id newPicture;
NXSize theSize,tempSize;
NXRect tempRect;
int tempPictureType;
char *rightmostPeriod;
if (pictureName == NULL) return NO;
rightmostPeriod = strrchr(pictureName, '.');
if (rightmostPeriod == NULL) return NO;
tempPictureType = TYPENONE;
if (NXOrderStrings((unsigned char *) ".tiff",(unsigned char *) rightmostPeriod, YES, 5, NULL) == 0)
tempPictureType = TYPETIFF;
if (NXOrderStrings((unsigned char *) ".eps",(unsigned char *) rightmostPeriod, YES, 4, NULL) == 0)
tempPictureType = TYPEEPS;
if (tempPictureType == TYPENONE) return NO; // unsuccessful load
input = NXMapFile(pictureName, NX_READONLY);
if (input == NULL) return NO; // unsuccessful load
newPicture = [[NXImage alloc] initFromStream:input];
[newPicture getSize:(NXSize *)&theSize];
/* if a tiff picture is too small, it is rejected */
/* (We could just resize it, as we do for eps pictures, */
/* but I don't like the look of resized tiffs anyways.) */
if (((theSize.height < IMAGEMINHEIGHT) || (theSize.width < IMAGEMINWIDTH))
&& (tempPictureType == TYPETIFF))
{
[newPicture free];
NXCloseMemory(input,NX_FREEBUFFER);
return NO; // unsuccessful load
}
/* we now know we have a valid picture, so get rid of the old one */
[picture free];
picture = newPicture;
/* if a tiff picture is too large, use only the bottom left section */
if (((theSize.height > IMAGEMAXHEIGHT) || (theSize.width > IMAGEMAXWIDTH))
&& (tempPictureType == TYPETIFF))
{
if (theSize.width > IMAGEMAXWIDTH)
tempRect.size.width = IMAGEMAXWIDTH;
else
tempRect.size.width = theSize.width;
if (theSize.height > IMAGEMAXHEIGHT)
tempRect.size.height = IMAGEMAXHEIGHT;
else
tempRect.size.height = theSize.height;
tempRect.origin.x = 0;
tempRect.origin.y = 0;
picture = [[NXImage alloc] initFromImage:(NXImage *)newPicture rect:(const NXRect *)&tempRect];
}
/* if an eps picture is too small, we resize it */
if (((theSize.height < IMAGEMINHEIGHT) || (theSize.width < IMAGEMINWIDTH))
&& (tempPictureType == TYPEEPS))
{
if (theSize.width < IMAGEMINWIDTH)
tempSize.width = IMAGEMINWIDTH;
else
tempSize.width = theSize.width;
if (theSize.height < IMAGEMINHEIGHT)
tempSize.height = IMAGEMINHEIGHT;
else
tempSize.height = theSize.height;
[newPicture setSize:&tempSize];
}
/* if an eps picture is too large, we resize it */
if (((theSize.height > IMAGEMAXHEIGHT) || (theSize.width > IMAGEMAXWIDTH))
&& (tempPictureType == TYPEEPS))
{
if (theSize.width > IMAGEMAXWIDTH)
tempSize.width = IMAGEMAXWIDTH;
else
tempSize.width = theSize.width;
if (theSize.height > IMAGEMAXHEIGHT)
tempSize.height = IMAGEMAXHEIGHT;
else
tempSize.height = theSize.height;
[newPicture setSize:&tempSize];
}
/* set eps pictures to be scalable */
if (tempPictureType == TYPEEPS) {
[picture setScalable:(BOOL)YES];
[picture setDataRetained:(BOOL)YES];
}
pictureType = tempPictureType;
pictureLoaded = YES;
NXCloseMemory(input,NX_FREEBUFFER);
NXWriteDefault ([NXApp appName], "Picture", filePath);
return YES;
}
- windowWillResize:sender toSize:(NXSize *)frameSize
// Acts as delegate for main window confining window resizing
{
if ( useNumbers == YES)
{
if (frameSize->width < (NXCoord)(NUMBERMINWIDTH+leftMargin+rightMargin+2))
frameSize->width = (NXCoord)(NUMBERMINWIDTH+leftMargin+rightMargin+2);
if (frameSize->height < (NXCoord)(NUMBERMINHEIGHT+23+9+topMargin+bottomMargin))
frameSize->height = (NXCoord)(NUMBERMINHEIGHT+23+9+topMargin+bottomMargin);
}
if ( usePictures == YES)
if (pictureType == TYPEEPS) {
if (frameSize->width < (NXCoord)(IMAGEMINWIDTH+leftMargin+rightMargin+2))
frameSize->width = (NXCoord)(IMAGEMINWIDTH+leftMargin+rightMargin+2);
if (frameSize->height < (NXCoord)(IMAGEMINHEIGHT+23+9+topMargin+bottomMargin))
frameSize->height = (NXCoord)(IMAGEMINHEIGHT+23+9+topMargin+bottomMargin);
if (frameSize->width > (NXCoord)(IMAGEMAXWIDTH+leftMargin+rightMargin+2))
frameSize->width = (NXCoord)(IMAGEMAXWIDTH+leftMargin+rightMargin+2);
if (frameSize->height > (NXCoord)(IMAGEMAXHEIGHT+23+9+topMargin+bottomMargin))
frameSize->height = (NXCoord)(IMAGEMAXHEIGHT+23+9+topMargin+bottomMargin);
}
else { // I don't like the look of resized tiffs, so don't allow resizing
frameSize->width = (windowSize.width+2);
frameSize->height = (windowSize.height+23+9);
}
return self;
}
- windowDidResize:sender;
// Acts as delegate for main window. Enable or disable usePicturesSwitch and
// useNumbersSwitch according to new size. Resize picture and redraw.
{
NXRect theRect;
NXSize theSize;
[tileSlideCV getFrame:(NXRect *)&theRect];
theSize.width = theRect.size.width - leftMargin - rightMargin;
theSize.height = theRect.size.height - topMargin - bottomMargin;
if ((theSize.height >= IMAGEMINHEIGHT) && (theSize.width >= IMAGEMINWIDTH)
&& (theSize.height <= IMAGEMAXHEIGHT) && (theSize.width <= IMAGEMAXWIDTH)
&& (pictureLoaded == YES) && (useNumbers == TRUE))
[usePicturesSwitch setEnabled:YES];
else
[usePicturesSwitch setEnabled:NO];
if ((theSize.height >= NUMBERMINHEIGHT) && (theSize.width >= NUMBERMINWIDTH)
&& (usePictures == TRUE))
[useNumbersSwitch setEnabled:YES];
else
[useNumbersSwitch setEnabled:NO];
if ( usePictures == NO) {
if (pictureType == TYPEEPS)
[picture setSize:&theSize];
[self sizeSlideBoard];
}
else
if (pictureType == TYPEEPS) {
[picture setSize:&theSize];
[self sizeSlideBoard];
}
return self;
}
@end